import { app, BrowserWindow, dialog, ipcMain } from 'electron';
import path from 'node:path';
import fs from 'node:fs';
import child_process from 'node:child_process';
import axios from 'axios';

// 应用入口函数
function createWindow(dataPath:string):BrowserWindow{
    // 打开新窗口
    const mainWindow=new BrowserWindow({
        width:800,
        height:600,
        backgroundColor:'green',
        webPreferences:{
            preload:path.join(__dirname,'preload.js')// 加载预处理器脚本
        }
    });
    if(app.isPackaged){
        // 生产环境加载文件
        mainWindow.loadFile(path.join(__dirname,'index.html'));
        // 移除窗口菜单栏
        mainWindow.removeMenu();
    }else{
        // 开发环境加载URL
        mainWindow.loadURL('http://localhost:5173');
        // 打开浏览器开发者工具
        mainWindow.webContents.openDevTools();
    }
    // 初始化数据库并加载数据到主进程内存
    const data=JSON.parse(fs.readFileSync(dataPath).toString());
    global.db={data};
    return mainWindow;
}

// 浏览数据库方法
function browseDatabase():JSON{
    return global.db.data;
}

// 计算编辑距离方法
function editDistance(text1:string,text2:string):number{
    const cols:number=text1.length+1;
    const rows:number=text2.length+1;
    const matrix:number[][]=[];
    for(let i=0;i<rows;i++){
        matrix.push(Array(cols).fill(0));
    }
    for(let i=0;i<cols;i++){
        matrix[0][i]=i
    }
    for(let i=0;i<rows;i++){
        matrix[i][0]=i;
    }
    for(let i=1;i<rows;i++){
        for(let j=1;j<cols;j++){
            matrix[i][j]=Math.min(
                matrix[i-1][j-1],
                matrix[i-1][j],
                matrix[i][j-1]
            )+Number(text1[j-1] !== text2[i-1]);
        }
    }
    return matrix[rows-1][cols-1];
}

// 判断字符串是否相似方法
function stringSimilar(text1:string,text2:string):boolean{
    // 根据编辑距离计算字符串相似度
    const similarity:number=1-editDistance(text1,text2)/Math.max(text1.length,text2.length);
    if(text1.length * text2.length === 0){
        return false;
    }
    // 相似度大于80%或其中一个字符串为另一个字符串的子串则认为相似
    return (similarity >= 0.8) || text1.includes(text2) || text2.includes(text1);
}

// 搜索报错函数
function searchError():void{
    dialog.showMessageBox({
        title:'ERROR',
        type:'error',
        message:'Query is empty!'
    });
}

// 搜索数据方法
function searchData(_event:Event,method:string,query:string|string[]):string[]|false{
    // 当查询为空时报错
    if(typeof query === 'string' && query.trim() === ''){
        searchError();
        return false;
    }
    if(query instanceof Array && query.every(item=>item.trim() === '')){
        searchError();
        return false;
    }
    // 初始化查询结果
    const results:string[]=[];
    if(method === 'blast'){
        // 根据应用打包状态生成blast工具目录路径
        const blastDir:string=app.isPackaged?'../blast':'blast';
        // 准备blast搜索输入
        const queryPath:string=path.join(__dirname,blastDir,'query.fa');
        fs.writeFileSync(queryPath,query as string);
        // 生成blast工具路径
        const blastPath:string=path.join(__dirname,blastDir,'blastn.exe');
        // 生成查询数据库路径
        const databasePath:string=path.join(__dirname,blastDir,'database');
        // 生成输出结果路径
        const outputPath:string=path.join(__dirname,blastDir,'result.txt');
        // 执行本地blast搜索
        const command:string=`${blastPath} -subject ${databasePath} -query ${queryPath} -evalue 1e-5 -out ${outputPath} -outfmt 6 -task blastn`
        child_process.execSync(command);
        // 读取blast搜索结果
        const content:string=fs.readFileSync(outputPath).toString();
        // 根据blast搜索结果生成无重复结果集合
        const resultSet=new Set();
        for(const line of content.split('\n').map(item=>item.trim()).filter(item=>item !== '')){
            resultSet.add(line.slice(0,4));
        }
        // 将结果集合内容写入查询结果
        for(const item of resultSet){
            results.push(item as string);
        }
    }else if(method === 'text'){
        // 获取查询数据集
        const dataset:any=browseDatabase();
        const data={...dataset.complexes,...dataset.ligands};
        // 对被查询数据集中每一项递归执行字符串相似判断
        for(const [key,value] of Object.entries(data)){
            if(stringSimilar(key,(query as string).trim())){
                results.push(key);
                continue;
            }
            for(const field in value as object){
                const content=value![field];
                if(content === null){
                    continue;
                }
                if(stringSimilar(content.trim(),(query as string).trim())){
                    results.push(key);
                    break;
                }
            }
        }
    }else{
        // 获取查询数据集
        const data=(browseDatabase() as any).complexes;
        // 对被查询数据集中每一项的指定字段进行精准匹配
        for(const [key,value] of Object.entries(data)){
            // 匹配查询项的分类字段
            if((query[0].length > 0) && ((value as any).Classification !== query[0])){
                continue;
            }
            // 匹配查询项的实验方法字段
            if(query[1].length > 0){
                const method=(value as any)['Experimental Method'];
                if(query[1] === 'X-RAY'){
                    if(!method.startsWith(query[1])){
                        continue;
                    }
                }else if(query[1] === 'NMR'){
                    if(!method.endsWith(query[1])){
                        continue;
                    }
                }else{
                    if(method.startsWith('X-RAY') || method.endsWith('NMR')){
                        continue;
                    }
                }
            }
            // 匹配查询项的配体列表字段
            const ligandQuery=query[2].split(',').map(item=>item.trim()).filter(item=>item !== '');
            if(ligandQuery.length > 0){
                const ligandList=(value as any).Ligands.split(',').map(item=>item.trim()).filter(item=>item !== '');
                if(!ligandQuery.every(item=>ligandList.includes(item))){
                    continue;
                }
            }
            // 所有指定字段均匹配的项会加入结果
            results.push(key);
        }
    }
    return results;
}

// 定义数据库复合物结构条目类型
interface ComplexItem{
    Title:string,
    Classification:string,
    Source:string,
    Mutation:string,
    'Experimental Method':string,
    Resolution:string,
    'Dot-Bracket':string,
    Ligands:string,
    'Binding Affinity':string|null,
    'Affinity Reference':string|null,
    'NALDB ID':string|null,
    'Refernce DOI':string|null,
    'Reference PMID':string|null,
    'Docking Energy':string|null,
    'Interaction Energy':string|null
}
// 定义数据库配体条目类型
interface LigandItem{
    Name:string,
    Formula:string,
    'Molecular Weight':string,
    'Isomeric SMILES':string,
    'InChI':string
}
// 定义数据库条目类型
type DataItem=ComplexItem|LigandItem;
// 匹配输入项方法
function matchEntry(_event:Event,query:string):DataItem{
    const dataset=browseDatabase();
    const data={...(dataset as any).complexes,...(dataset as any).ligands};
    return data[query];
}

// 自动更新方法
function autoUpdate(dataPath:string,appWindow:BrowserWindow):void{
    // 加载数据源网址
    const config:string=path.join(__dirname,app.isPackaged?'../datasource.json':'datasource.json');
    const url:string=JSON.parse(fs.readFileSync(config).toString()).url;
    // 拉取更新方法
    function pullUpdate():void{
        // 发送异步请求
        axios.get(url).then(result=>{
            // 从响应体中加载数据
            const dataset=result.data;
            // 当在线数据集版本比本地数据集版本新时
            if(dataset.version > global.db.data.version){
                // 更新内存中数据
                global.db.data=dataset;
                // 更新本地数据存档
                fs.writeFileSync(dataPath,dataset);
                // 向前端通知数据更新
                appWindow.webContents.send('update');
            }
        }).catch(error=>{
            console.log(error.message);
        });
    }
    // 直接拉取一次更新
    pullUpdate();
    // 定期拉取更新
    setInterval(pullUpdate,1000*60*60);
}

// 启动应用
app.whenReady().then(()=>{
    // 加载数据库存档路径
    const dataPath:string=path.join(__dirname,app.isPackaged?'../dataset.json':'dataset.json');
    // 创建新窗口并初始化数据库
    const mainWindow=createWindow(dataPath);
    // 配置自动更新
    autoUpdate(dataPath,mainWindow);
    // 挂载事件处理器
    ipcMain.handle('browse',browseDatabase);
    ipcMain.handle('search',searchData);
    ipcMain.handle('match',matchEntry);
    // 主窗口关闭时应用退出
    mainWindow.on('close',app.quit);
});
